import * as array from './array'

function isPromise(value: any) {
  return value instanceof Promise
}

export const sleep = (milliseconds: number) => {
  return new Promise((res) => setTimeout(res, milliseconds))
}

export const tryit = (func: any) => {
  return (...args: any) => {
    try {
      const result = func(...args)
      if (isPromise(result)) {
        return result
          .then((value: any) => [void 0, value])
          .catch((err: any) => [err, void 0])
      }
      return [void 0, result]
    } catch (err) {
      return [err, void 0]
    }
  }
}
export const parallel = async (limit: number, array$1: any[], func: any) => {
  const work = array$1.map((item, index) => ({
    index,
    item,
  }))
  const processor = async (res: any) => {
    const results2 = []
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const next = work.pop()
      if (!next) return res(results2)
      const [error, result] = await tryit(func)(next.item)
      results2.push({
        error,
        result,
        index: next.index,
      })
    }
  }
  const queues = array.list(1, limit).map(() => new Promise(processor))
  const itemResults = await Promise.all(queues)
  const [errors, results] = array.fork(
    array.sort(itemResults.flat(), (r: any) => r.index),
    (x: any) => !!x.error,
  )
  if (errors.length > 0) {
    throw new Error(errors.map((error: any) => error.error))
  }
  return results.map((r: any) => r.result)
}

export const retry = async (options: any, func: any) => {
  const times = options?.times ?? 3
  const delay = options?.delay
  const backoff = options?.backoff ?? null
  for (const i of array.range(1, times)) {
    const [err, result] = await tryit(func)((err2: any) => {
      throw { _exited: err2 }
    })
    if (!err) return result
    if (err._exited) throw err._exited
    if (i === times) throw err
    if (delay) await sleep(delay)
    if (backoff) await sleep(backoff(i))
  }
  return void 0
}
